#include <buola/cv/barcode/decode.h>
#include <numeric>

namespace buola { namespace cvi { namespace barcode {

static const int sI25Sym[32]=
{
    -1,-1,-1, 7,-1, 4, 0,-1,
    -1, 2, 9,-1, 6,-1,-1,-1,
    -1, 1, 8,-1, 5,-1,-1,-1,
     3,-1,-1,-1,-1,-1,-1,-1    
};

static inline int i25_get_symbol(const int* pPtr,int p3SymW,int p7SymW,int lAvgDiff)
{
    int lSym1=0,lSym2=0;
    int l28BarW=(pPtr[0]+lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym1|=(1<<4);
    l28BarW=(pPtr[1]-lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym2|=(1<<4);
    l28BarW=(pPtr[2]+lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym1|=(1<<3);
    l28BarW=(pPtr[3]-lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym2|=(1<<3);
    l28BarW=(pPtr[4]+lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym1|=(1<<2);
    l28BarW=(pPtr[5]-lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym2|=(1<<2);
    l28BarW=(pPtr[6]+lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym1|=(1<<1);
    l28BarW=(pPtr[7]-lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym2|=(1<<1);
    l28BarW=(pPtr[8]+lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym1|=(1<<0);
    l28BarW=(pPtr[9]-lAvgDiff)*28;
    if(l28BarW>=p7SymW) return -1;
    if(l28BarW>=p3SymW) lSym2|=(1<<0);
    lSym1=sI25Sym[lSym1];
    if(lSym1<0) return -1;
    lSym2=sI25Sym[lSym2];
    if(lSym2<0) return -1;
    return (lSym1<<4)+lSym2;
}

static inline bool i25_check_mark_left(const int *pPtr,int p3SymW,int p7SymW,bool pStart,int pAvgDiff)
{
    if(pStart) //start mark
    {
        int l28BarW=(pPtr[0]-pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        l28BarW=(pPtr[-1]+pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        l28BarW=(pPtr[-2]-pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        l28BarW=(pPtr[-3]+pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        if((pPtr[-4]-pAvgDiff)*9<=p3SymW) return false;
    }
    else
    {
        int l28BarW=(pPtr[0]-pAvgDiff)*28;
        if(l28BarW>=p7SymW||l28BarW<p3SymW) return false;
        l28BarW=(pPtr[-1]+pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        l28BarW=(pPtr[-2]-pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        if((pPtr[-3]+pAvgDiff)*9<=p3SymW) return false;
    }
    return true;
}

static inline bool i25_check_mark_right(const int *pPtr,int p3SymW,int p7SymW,bool pStart,int pAvgDiff)
{
    if(pStart) //start mark
    {
        int l28BarW=(pPtr[0]+pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        l28BarW=(pPtr[1]-pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        l28BarW=(pPtr[2]+pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        l28BarW=(pPtr[3]-pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        if((pPtr[4]+pAvgDiff)*6<=p3SymW) return false;
    }
    else
    {
        int l28BarW=(pPtr[0]+pAvgDiff)*28;
        if(l28BarW>=p7SymW||l28BarW<p3SymW) return false;
        l28BarW=(pPtr[1]-pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        l28BarW=(pPtr[2]+pAvgDiff)*28;
        if(l28BarW>=p3SymW) return false;
        if((pPtr[3]-pAvgDiff)*6<=p3SymW) return false;
    }
    return true;
}

static const int sReverse[]=
{
    6,  1,  8,  7,  5,  4,  0,  3,  2,  9
};

static inline std::string syms_to_text(int *pSyms,int pN,bool pReverse)
{
    std::string lString(pN*2,' ');
    if(pReverse)
    {
        for(int i=0;i<pN;i++)
        {
            lString[i*2+1]='0'+sReverse[pSyms[pN-1-i]>>4];
            lString[i*2]='0'+sReverse[pSyms[pN-1-i]&0xf];
        }
    }
    else
    {
        for(int i=0;i<pN;i++)
        {
            lString[i*2]='0'+(pSyms[i]>>4);
            lString[i*2+1]='0'+(pSyms[i]&0xf);
        }
    }
    
    return lString;
}

void decode_i25(const std::vector<int> &pWidths,std::vector<CSymbol> &pSymbols)
{
    int lSyms[50];
    
    const int *lBegin=&pWidths.front();
    const int *lEnd=&pWidths.back()+1;
    
    bool lStart=true;
    for(const int *lPtr=lBegin+13;lPtr<lEnd;++lPtr)
    {
        int lFirstW=lPtr[-1]+lPtr[-3]+lPtr[-5]+lPtr[-7]+lPtr[-9];
        int lSecondW=lPtr[0]+lPtr[-2]+lPtr[-4]+lPtr[-6]+lPtr[-8];
        int lAvgDiff=(lSecondW-lFirstW)/10;
        
        int lSymW=lFirstW+lSecondW;
        lStart=!lStart;
        int l3SymW=3*lSymW;
        int l7SymW=7*lSymW;
        if(!i25_check_mark_left(lPtr-10,l3SymW,l7SymW,lStart,lAvgDiff)) continue;

        int j=0;
        const int *lTmp;
        for(lTmp=lPtr-9;lTmp+9<lEnd;lTmp+=10)
        {
            //maybe we should readjust the symbol width
            lSyms[j]=i25_get_symbol(lTmp,l3SymW,l7SymW,lAvgDiff);
            if(lSyms[j]<0) break;
            ++j;
        }
        //now lTmp points to the end of the last symbol
        if(!j) continue;
        if(lStart) //we must now read an end, so we need at least 3 more widths
        {
            if(lTmp+3>=lEnd) continue;
        }
        else
        {
            if(lTmp+4>=lEnd) continue;
        }
        if(!i25_check_mark_right(lTmp,l3SymW,l7SymW,!lStart,lAvgDiff)) continue;

        if(j<3) continue; //reject too short symbols
        
        CSymbol lSym;
        lSym.mB=std::accumulate(lBegin,lPtr-10,0)>>5;
        lSym.mE=lSym.mB+(std::accumulate(lPtr-10,lTmp,0)>>5);
        lSym.mText=syms_to_text(lSyms,j,!lStart);
        lSym.mType=EType::I25;
        lSym.mDirection=lStart?1:-1;
        
        pSymbols.push_back(lSym);

        lPtr=lTmp;
    }
}
    
/*namespace barcode*/ } /*namespace cvi*/ } /*namespace buola*/ }
